package domain

import (
	"cvevulner/cve-ddd/domain/dp"
	"cvevulner/util"
)

type Cves []Cve

//CvesByComponent is group of cves by component
type CvesByComponent []Cve

//CvesByVersion is group of CvesByComponent by version
type CvesByVersion []Cve

type Cve struct {
	Component       string
	Description     string
	SeverityLevel   string
	AffectedVersion []string
	AffectedProduct string
	OpeneulerScore  float64
	Theme           string
	CveNum          string
	CveBrief        string
	OpeneulerVector string
	CveVersion      string
	HotIssueNum     string
	AbiVersion      string
	ColdIssue       Issue
}

type Issue struct {
	Number string
	Repo   string
}

func (d Cve) isAffectVersion(version string) bool {
	for _, v := range d.AffectedVersion {
		if v == version {
			return true
		}
	}

	return false
}

func (ds Cves) GroupByVersion() map[string]CvesByVersion {
	group := make(map[string]CvesByVersion)
	for _, cve := range ds {
		group[cve.AffectedVersion[0]] = append(group[cve.AffectedVersion[0]], cve)
	}

	return group
}

//GroupByComponent group cves by component
func (ds Cves) groupByComponent() map[string]CvesByComponent {
	group := make(map[string]CvesByComponent)
	for _, d := range ds {
		group[d.Component] = append(group[d.Component], d)
	}

	return group
}

//GenerateBulletins CvesByComponent is a component-differentiated set of cves,
//Bulletins are consolidated into one when all issues of a component affect all versions currently maintained,
//otherwise they are split into multiple bulletins by version
func (ds Cves) GenerateBulletins() []SecurityBulletin {
	var securityBulletins []SecurityBulletin

	for _, dsc := range ds.groupByComponent() {
		if dsc.isCombined() {
			securityBulletins = append(securityBulletins, dsc.combinedBulletin())
		} else {
			securityBulletins = append(securityBulletins, dsc.separatedBulletins()...)
		}
	}

	return securityBulletins
}

//IsCombined determine whether multiple cves under the same component
//need to be combined into a single bulletin
func (dsc CvesByComponent) isCombined() bool {
	for _, d := range dsc {
		if len(d.AffectedVersion) != len(dp.MaintainVersion) {
			return false
		}

		for _, version := range d.AffectedVersion {
			if !dp.MaintainVersion[version] {
				return false
			}
		}
	}

	return true
}

//CombinedBulletin put all cves in one bulletin
func (dsc CvesByComponent) combinedBulletin() SecurityBulletin {
	return SecurityBulletin{
		AffectedVersion: dsc[0].AffectedVersion,
		Date:            util.Date(),
		Component:       dsc[0].Component,
		Cves:            Cves(dsc),
	}
}

// SeparatedBulletins split into multiple bulletins by version
func (dsc CvesByComponent) separatedBulletins() []SecurityBulletin {
	var sbs []SecurityBulletin
	for version, ds := range dsc.separateByVersion() {
		sbs = append(sbs, ds.bulletinByVersion(version))
	}

	return sbs
}

func (dsc CvesByComponent) separateByVersion() map[string]CvesByVersion {
	classifyByVersion := make(map[string]CvesByVersion)
	for version := range dp.MaintainVersion {
		for _, d := range dsc {
			if d.isAffectVersion(version) {
				classifyByVersion[version] = append(classifyByVersion[version], d)
			}
		}
	}

	return classifyByVersion
}

func (dsv CvesByVersion) bulletinByVersion(version string) SecurityBulletin {
	return SecurityBulletin{
		AffectedVersion: []string{version},
		Date:            util.Date(),
		Component:       dsv[0].Component,
		Cves:            Cves(dsv),
	}
}
